-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local signal = require 'mc.signal'
local ctx = require 'mc.context'
local log = require 'mc.logging'
local utils = require 'mc.utils'
local mdb = require 'mc.mdb'
local debounce = require 'mc.debounce.debounce'
local bs = require 'mc.bitstring'
local crc8 = require 'mc.crc8'
local c_object = require 'mc.orm.object'
local utils_core = require 'utils.core'
local c_storageconfig = require 'storageconfig.storageconfig_object'
local storage_bus = require 'storage_bus'
local common_def = require 'common_def'
local nvme_utils = require 'nvme.utils'
local c_ssd_form_factor = require 'nvme.sff_protocol.ssd_form_factor'
local nvme_mctp = require 'nvme.nvme_mi_protocol.nvme_mi_mctp'
local nvme_admin_command = require 'nvme.nvme_mi_protocol.nvme_mi_admin_command'
local nvme_mi_command = require 'nvme.nvme_mi_protocol.nvme_mi_command'
local nvme_admin_command_process = require 'nvme.nvme_mi_protocol.nvme_admin_command_process'
local NVME_MI_OPTIONS = require 'nvme.nvme_mi_protocol.nvme_mi_def'
local replica_drive_collection = require 'nvme.replica_drive.replica_drive_collection'
local smart_def = common_def.PD_SMART_DEF

local ALARM_DELAY_TIME <const> = 6  -- 硬盘10s更新一次状态，硬盘在插入后，有一分钟硬盘状态不正确，需要延时一分钟后，在获取状态来告警
local MI_FAILURE_OFFSET <const> = 1
local MI_PRE_FAILURE_OFFSET <const> = 2
local SSD_FIRMWARE_LEN <const> = 8
local SSD_FIRMWARE_OFFSET <const> = 39
local SMBIOS_WRITE_FINISH <const> = 3
local INVALID_READING <const> = 0
local TASK_UPDATE_NVME_EP <const> = 'update_nvme_ep'
local TASK_UPDATE_NVME_MCTP <const> = 'update_nvme_mctp'
local TASK_DUMP_WRITE_AMP <const> = 'dump_write_amp'
local UPDATE_INTERVAL_24_HOURS <const> = (3600000 * 24)     --24小时更新一次
local UPDATE_INTERVAL_30_MINS <const> = 1800000     -- 半小时判断一次是否需要记录写放大信息
local MCTP_SUPPORT = 1
local VENDOR_SPECIFIC = 6
local MULTIRECORDAREA = 11
local PORTMULTIRECORD = 12

local subsystem_mgmt_data <const> = bs.new([[<<
    length,
    sflgs,
    smart_warnings,
    ctemp,
    pdlu,
    cur_over_temp_warning_th,
    cur_power,
    pec
>>]])

local speed_table <const> = {
    [5] = 2.5,
    [6] = 5.0,
    [7] = 8.0,
    [8] = 10.0,
    [9] = 16.0,
    [10] = 20.0,
    [11] = 30.0,
    [12] = 32.0,
    [13] = 40.0,
    [14] = 64.0,
    [15] = 80.0,
    [16] = 96.0,
    [17] = 128.0,
    [18] = 160.0,
    [19] = 256.0
}

---@class c_nvme: c_object
---@field Slot integer
---@field PredictedMediaLifeLeftPercent integer
---@field Revision string
---@field MediaType integer
---@field Protocol integer
---@field Failure integer
---@field PredictiveFailure integer
---@field TemperatureCelsius integer
---@field VPDChip c_object
---@field SSDChip c_object
---@field RefComponent c_object
local c_nvme = c_object('Nvme') -- Nvme 类，主键是 Slot 字段

-- 解析从芯片中读取的二进制字符串
local function string_split(str, seq, numeration)
    if not str then
        return
    end

    local a = {}
    for word in string.gmatch(str, '[^{' .. seq .. '}*]+') do
        table.insert(a, tonumber(word, numeration))
    end

    return a
end

-- 将数字转为指定长度的二进制数table
local function byte2bin(n, len)
    local t = {}
    for i = len - 1, 0, -1 do
      t[#t+1] = math.floor(n / 2^i)
      n = n % 2^i
    end
    return t
end

-- 将二进制字符串取反
local function negation(number, length)
    local bit_arr = byte2bin(number, length)
    for k, v in pairs(bit_arr) do
        bit_arr[k] = v == 0 and 1 or 0
    end

    return tonumber(table.concat(bit_arr), 2)
end

-- 将nvme盘的速率转换为枚举值
local function nvme_max_speed2enum(nvme_max_speed_db)
    for k, v in pairs(speed_table) do
        if v == nvme_max_speed_db then
            return k
        end
    end
    return common_def.INVALID_U8
end

local function get_bit(val, bit)
    return (val & 1 << bit) > 0 and 1 or 0
end

function c_nvme:restore_status_flags()
    self.drive_not_ready = 1
    self.drive_not_ready_debounce:clear_debounced_val()
    self.drive_functional = 0
    self.drive_functional_debounce:clear_debounced_val()
    self.reset_not_required = 0
    self.port0_pcie_link_active = 0
    self.port0_pcie_link_active_debounce:clear_debounced_val()
    self.link_fault = 0
    self.link_fault_debounce:clear_debounced_val()
end

-- 根据NVM Express Basic Management Command获取Status Flags
function c_nvme:get_status_flags()
    if not self.SSDChip or not self.VPDChip then
        log:info('invalid ssd_chip or vpdchip')
        return
    end

    -- 偏移0读8个字节的数据
    local CMD_CODE <const> = 0
    local READ_LEN <const> = 8
    local ok, data = pcall(function ()
        return self.SSDChip:Read(ctx.get_context_or_default(), CMD_CODE, READ_LEN)
    end)
    if not ok then
        log:info('get_status_flags chip read failed')
        return
    end

    -- 0xD4为第一地址，0x00为cmd_code，0xD5为第二地址。详见nvme-mi协议文档说明
    local pec = crc8('\xD4\x00\xD5' .. data:sub(1, READ_LEN - 1))
    local res = subsystem_mgmt_data:unpack(data, true)
    -- 校验数据，长度必须为6
    if res.length ~= 6 or res.pec ~= pec then
        log:info('invalid data')
        return
    end
    local drive_not_ready = get_bit(res.sflgs, 6)
    local drive_functional = get_bit(res.sflgs, 5)
    local port0_pcie_link_active = get_bit(res.sflgs, 3)
    self.drive_not_ready = self.drive_not_ready_debounce:get_debounced_val(drive_not_ready)
    self.drive_functional = self.drive_functional_debounce:get_debounced_val(drive_functional)
    self.reset_not_required = get_bit(res.sflgs, 4)
    self.port0_pcie_link_active = self.port0_pcie_link_active_debounce:get_debounced_val(port0_pcie_link_active)
    local link_fault = (drive_not_ready == 0 and drive_functional == 1 and port0_pcie_link_active == 0) and 1 or 0
    if self.link_fault ~= link_fault then
        log:notice('======nvme:%s link_fault:%s, sflgs:%s' , self.Slot, link_fault, res.sflgs)
    end
    self.link_fault = self.link_fault_debounce:get_debounced_val(link_fault)
    -- Nvme Express Management Interface Spectification Revision 1.2, Figure 176
    -- smart_waring的值为1代表无告警，需要反转为0
    self.Status = tonumber(res.smart_warnings) ~ 0xff -- 这个就是异或运算
    self.LifeUsedPercentage = res.pdlu
    return
end

-- 检测NVMe盘是否使能
function c_nvme:pciessd_is_enable()
    if self.port0_pcie_link_active == 0 then
        return false
    end

    if self.drive_functional == 0 or self.drive_not_ready == 1 then
        return false
    end

    if self.reset_not_required == 0 then
        return false
    end

    return true
end

function c_nvme:reset_default_fail_val()
    self.last_fail = 0
    self.failure_debounce:clear_debounced_val()
    self.last_pre_fail = 0
    self.pre_failure_debounce:clear_debounced_val()
end

-- 处理 pcie predictive failure、fault、link faulty告警
function c_nvme:pciessd_predictive_failure_and_fault_alarm()
    -- 上电后才会产生预告警和告警
    -- OFFING到OFF的阶段处于SmbiosStatus为3，此时读值为0会触发误告警，通过power_on加强判断
    if  c_storageconfig.get_instance().smbios_status ~= SMBIOS_WRITE_FINISH or
        c_storageconfig.get_instance().power_state ~= 'ON' then
        self.update_time = 0
        self:restore_status_flags()
        self:reset_default_fail_val()
        return 0, 0
    end

    if self.protocol == common_def.NVME_VPD_PROTOCOL_NVME_MI then
        self:get_status_flags()
    end
    if self.update_time <= ALARM_DELAY_TIME * 5 then
        self.update_time = self.update_time + 1
        self:reset_default_fail_val()
        return 0, 0
    end

    if self.update_time <= ALARM_DELAY_TIME * 6 then
        self.update_time = self.update_time + 1
        if not self:pciessd_is_enable() then
            log:notice('nvme%s pciessd is disabled', self.Slot)
            self:reset_default_fail_val()
            return 0, 0
        end

        return self:get_nvme_pre_failure(), 0
    end
    return self:get_nvme_pre_failure(), self:get_nvme_failure()

end

-- 从 chip 中识别协议类型
function c_nvme:pcie_nvme_get_protocol_type()
    local ok, ret = pcall(function ()
        if self.VPDChip then
            local hex_str = utils.to_hex(self.VPDChip:Read(ctx.get_context_or_default(), 0, 
                common_def.NVME_COMMON_HEADER_LEN))
            local number_arr = string_split(hex_str, ' ', 16)
            local header_sum = 0
            for i = 1,  common_def.NVME_COMMON_HEADER_LEN -1 do
                header_sum = header_sum + number_arr[i]
            end
            header_sum = negation(header_sum, 8) % 256 + 1
            if header_sum == number_arr[common_def.NVME_COMMON_HEADER_LEN] then
                -- NVMe-mi协议
                log:notice('NVMe%s load nvme-mi', self.Slot)
                return 0
            else
                log:notice('NVMe%s load other protocols', self.Slot)
                return 1
            end
        else
            log:error('nvme VPDChip don\'t exist')
        end
    end)

    if ok and ret then
        return ret
    end

    return common_def.INVALID_U8
end

function c_nvme:get_info_offset_len(info_type)
    local next_item_offset = common_def.PRODUCT_INFO_MANUFACTURER_LEN_OFFSET
    local ok, ret = pcall(function (...)
        if not self.VPDChip then
            return
        end
        -- 获取区域的偏移地址
        return string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(), 
            common_def.COMMON_HEADER_PRODUCT_AREA_OFFSET, 1))
    end)

    if not ok or not ret then
        log:notice('get area offset failed')
        return
    end

    local area_offset = ret
    if area_offset > common_def.MAX_AREA_OFFSET_IN_HEADER then
        log:error('invalid offset(header) %d read from %s', area_offset, self.Slot)
        return
    end

    area_offset = area_offset * common_def.NVME_MI_AREA_OFFSET_MULTIPLE
    next_item_offset = next_item_offset + area_offset

    for i = 0, info_type do
        ok, ret = pcall(function ()
            return string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(), next_item_offset, 1))
        end)

        if not ok or not ret then
            log:notice('next_item_offset %d value invalid', next_item_offset)
            return
        end

        local item_bit_arr = byte2bin(ret, 8)
        -- 字节后六位为type_len
        local type_len = tonumber(table.concat(item_bit_arr, '', 3), 2)
        if i == info_type then
            next_item_offset = type_len ~= 0 and (next_item_offset + 1) or next_item_offset
            if next_item_offset > common_def.INVALID_U8 then
                log:error('invalid item offset %d', next_item_offset)
                return
            end
            return next_item_offset, type_len
        end

        next_item_offset = next_item_offset + type_len + 1
        if next_item_offset > common_def.INVALID_U8 then
            log:error('invalid item length %d', type_len)
            return
        end
    end

    return
end

function c_nvme:vpd_nvme_mi_get_product_info(info_type)
    local offset, len = self:get_info_offset_len(info_type)
    -- 根据 offset 和 length 从VPD中读取内容
    local ok, ret = pcall(function()
        if not self.VPDChip then
            return
        end

        local hex_str = utils.to_hex(self.VPDChip:Read(ctx.get_context_or_default(), offset, len))
        local hex_arr = string_split(hex_str, ' ', 16)
        local str = ''
        for _, v in pairs(hex_arr) do
            -- ASCII数字转字符
            if v ~= 0 then
                str = str .. string.char(v)
            end
        end
        return str
    end)

    if not ok or not ret then
        log:notice('Get %s value from chip failed', info_type)
        return
    end

    -- 过滤首尾空字符
    return ret:gsub("^%s*(.-)%s*$", "%1")
end

-- 根据厂商信息解析 vendor id
function c_nvme:vpd_nvme_mi_get_vender_id(manufacture)
    local manuf = string.lower(manufacture)
    if manuf == 'intel' then
        local ret = self:vpd_nvme_mi_get_product_info(common_def.RECORD_ITEM_PRODUCT_NAME) or common_def.INVALID_STRING
        if ret == 'P4600' or ret == 'P4610' or ret == 'P5510' then
            return common_def.NVME_VPD_VENDOR_ID_FOR_INTEL_P4600
        elseif ret == 'P5800X' then
            return common_def.NVME_VPD_VENDOR_ID_FOR_INTEL_P5800X
        else
            return common_def.NVME_VPD_VENDOR_ID_FOR_INTEL
        end
    end

    if manuf == 'samsung' then
        local ret = self:vpd_nvme_mi_get_product_info(common_def.RECORD_ITEM_PRODUCT_NAME) or common_def.INVALID_STRING
        if ret == 'PM9A3' then
            return common_def.NVME_VPD_VENDOR_ID_FOR_SAMSUNG_PM9A3
        end

        if ret == 'PM9D3a' then
            return common_def.NVME_VPD_VENDOR_ID_FOR_SAMSUNG_PM9D3A
        end
    end

    if manuf == "solidigm" then
        local ret = self:vpd_nvme_mi_get_product_info(common_def.RECORD_ITEM_PRODUCT_NAME) or common_def.INVALID_STRING
        if ret == 'D7-PS1010' or ret == 'D7-PS1030' then
            return common_def.NVME_VPD_VENDOR_ID_FOR_SOLIDIGM
        end
    end

    if manuf == 'memblaze' then
        local ret = self:vpd_nvme_mi_get_product_info(common_def.RECORD_ITEM_PRODUCT_NAME) or common_def.INVALID_STRING
        if ret == 'PBLAZE7' then
            return common_def.NVME_VPD_VENDOR_ID_FOR_MEMBLAZE_7940
        end
    end

    return common_def.MANUFACTURE_ID_MAP[manufacture]
end

-- 从NVM subsystem descriptor中获取连接速率/带宽的起始偏移和信息长度
function c_nvme:get_linkinfo_from_nvm_subsystem(start_offset)
    local element_type
    local element_offset = start_offset + common_def.TOPOLOGY_MULTIRECORD_FIRST_ELEMENT_OFFSET
    local ok, ret = pcall(function ()
        return string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(),
            start_offset + common_def.TOPOLOGY_MULTIRECORD_ELEMENT_COUNT_OFFSET, 1))
    end)

    if not ok or not ret then
        log:error('read chip failed')
        return
    end

    local element_count = ret
    for i = 0, element_count - 1 do
        ok, element_type = pcall(function ()
            return string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(), element_offset, 1))
        end)

        if not ok or not element_type then
            log:error('read chip failed')
            return
        end

        if element_type == common_def.ELEMENT_TYPE_NVM_SUBSYSTEM then
            local item_ret, port_count = pcall(function ()
                return string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(),
                    element_offset + common_def.NVM_SUBSYSTEM_PORT_COUNT_OFFSET, 1))
            end)
            if not item_ret or not port_count or port_count == 0 then
                log:error('Read chip fail or invalid port count')
                return
            end
            element_offset = element_offset + common_def.NVM_SUBSYSTEM_FIRST_PORT_LINK_SPEED_OFFSET
            if element_count > common_def.INVALID_U8 then
                log:error('invalid element offset %d', element_offset)
                return
            end

            return element_offset
        end

        local item_ret, element_len = pcall(function ()
            return string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(),
                element_offset + common_def.ELEMENT_DESCRIPTOR_LEN_OFFSET, 1))
        end)

        if not item_ret or element_len then
            log:error('Read chip failed')
            return
        end

        element_offset = element_offset + element_len
        if element_offset > common_def.INVALID_U8 then
            log:error('invalid element length %d', element_len)
            return
        end
    end
    return
end

-- 匹配multi record中指定信息，找到则返回偏移和长度
function c_nvme:multirecord_info_matched(info_type, record_offset, record_type)
    local item_offset = record_offset
    if info_type == common_def.RECORD_ITEM_CAPACITY then
        if record_type == common_def.MULTIRECORD_TYPE_NVME then
            item_offset = item_offset + common_def.MULTIRECORD_INFO_CAPACITY_OFFSET
            if item_offset > common_def.INVALID_U8 then
                log:error('invalid item offset %d', item_offset)
                return
            end
            return item_offset, common_def.MULTIRECORD_INFO_CAPACITY_LEN
        end
    elseif info_type == common_def.RECORD_ITEM_LINK_SPEED or info_type == common_def.RECORD_ITEM_LINK_WIDTH then
        if record_type == common_def.MULTIRECORD_TYPE_NVME_PCIE_PORT then
            log:notice('get disk%s NVMe MI 1.0', self.Slot)
            -- NVMe MI 1.0走这个分支
            item_offset = info_type == common_def.RECORD_ITEM_LINK_SPEED and
                item_offset + common_def.PCIE_PORT_MULTIRECORD_LINK_SPEED or
                item_offset + common_def.PCIE_PORT_MULTIRECORD_LINK_WIDTH
            if item_offset > common_def.INVALID_U8 then
                log:error('invalid item offset %d', item_offset)
                return
            end
            return item_offset, 1
        elseif record_type == common_def.MULTIRECORD_TYPE_TOPOLOGY then
            log:notice('get disk%s NVMe MI 1.1', self.Slot)
            -- NVMe MI 1.1走这个分支
            local ret = self:get_linkinfo_from_nvm_subsystem(record_offset)
            if not ret then
                log:error('get_linkinfo_from_nvm_subsystem failed')
                return
            end
            item_offset = info_type == common_def.RECORD_ITEM_LINK_WIDTH and ret + 1 or ret
            if item_offset > common_def.INVALID_U8 then
                log:error('invalid item offset %d', item_offset)
            end
            return item_offset, 1
        end
    end
    return
end

-- 从 multi record中获取指定信息的偏移和长度
function c_nvme:get_multirecord_item_offset_len(info_type)
    local record_type = 0
    local record_format = 0
    local record_len = 0
    local record_count = 0

    -- 获取MulitRecord区域的偏移地址
    local ok, ret = pcall(function ()
        local record_offset = string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(),
            common_def.COMMON_HEADER_MULTI_RECORD_AREA_OFFSET, 1))
        if record_offset > common_def.MAX_AREA_OFFSET_IN_HEADER then
            log:error('invalid offset(header) %d read from %s', record_offset, 'VPDChip' .. self.Slot)
            return
        end
        return record_offset
    end)

    if not ok or not ret then
        log:error('get record_offset from chip %d failed', self.Slot)
        return
    end

    local next_rec_offset = ret * common_def.NVME_MI_AREA_OFFSET_MULTIPLE
    local last_record = false
    repeat
        ok, _ = pcall(function ()
            record_type = string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(), next_rec_offset, 1))
            record_format = string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(), next_rec_offset + 1, 1))
            record_len = string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(), next_rec_offset + 2, 1))
        end)

        if not ok then
            log:error('get_multirecord_item_offset_len: Read chip failed')
            return
        end

        local offset, length = self:multirecord_info_matched(info_type, next_rec_offset, record_type)

        if offset and length then
            return offset, length
        end

        local record_format_arr = byte2bin(record_format, 8)
        -- 第2个字节第1个bit位是否为1
        last_record = record_format_arr[1] == 1 and true or false
        if record_type == common_def.MULTIRECORD_TYPE_NVME then
            next_rec_offset = next_rec_offset + common_def.NVME_MULTIRECORD_FIXED_LEN
        elseif record_type == common_def.MULTIRECORD_TYPE_NVME_PCIE_PORT then
            next_rec_offset = next_rec_offset + common_def.NVME_PCIE_PORT_MULTIRECORD_FIXED_LEN
        else
            next_rec_offset = next_rec_offset + record_len + common_def.MULTIRECORD_COMMON_HEADER_LEN
        end

        record_count = record_count + 1
        if record_count > common_def.MAX_MULTIRECORD_NUM or next_rec_offset > common_def.INVALID_U8 then
            log:error('invalid record count %d or offset %d', record_count, next_rec_offset)
            return
        end
    until last_record

    return
end

local function judge_bit_equal(source, target, len)
    local source_arr = byte2bin(source, len)
    local target_arr = byte2bin(target, len)
    for i = 1, len do
        if target_arr[i] == 1 and source_arr[i] ~= 1 then
            return false
        end
    end
    return true
end

-- 获取NVMe-MI 协议的SSD 盘的 link speed 信息
function c_nvme:vpd_nvme_mi_get_link_speed()
    local offset, _ = self:get_multirecord_item_offset_len(common_def.RECORD_ITEM_LINK_SPEED)
    local ok, speed = pcall(function ()
        return string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(), offset, 1))
    end)

    if not ok or not speed then
        log:error('Read chip failed')
        return
    end

    if judge_bit_equal(speed, 1 << common_def.NVME_MI_PCIE_GEN5_OFFSET, 8) then
        return common_def.NVME_MI_PCIE_GEN5_SPEED
    elseif judge_bit_equal(speed, 1 << common_def.NVME_MI_PCIE_GEN4_OFFSET, 8) then
        return common_def.NVME_MI_PCIE_GEN4_SPEED
    elseif judge_bit_equal(speed, 1 << common_def.NVME_MI_PCIE_GEN3_OFFSET, 8) then
        return common_def.NVME_MI_PCIE_GEN3_SPEED
    elseif judge_bit_equal(speed, 1 << common_def.NVME_MI_PCIE_GEN2_OFFSET, 8) then
        return common_def.NVME_MI_PCIE_GEN2_SPEED
    elseif judge_bit_equal(speed, 1 << common_def.NVME_MI_PCIE_GEN1_OFFSET, 8) then
        return common_def.NVME_MI_PCIE_GEN1_SPEED
    else
        return 0
    end
end

function c_nvme:vpd_nvme_mi_get_link_width()
    local offset, _ = self:get_multirecord_item_offset_len(common_def.RECORD_ITEM_LINK_WIDTH)
    local ok, link_width = pcall(function ()
        return string.unpack('B', self.VPDChip:Read(ctx.get_context_or_default(), offset, 1))
    end)

    if not ok or not link_width then
        log:error('vpd_nvme_mi_get_link_width:Read chip failed')
        return
    end

    return link_width
end

function c_nvme:vpd_nvme_mi_get_capacity()
    local offset, length = self:get_multirecord_item_offset_len(common_def.RECORD_ITEM_CAPACITY)

    local ok, capcacity = pcall(function ()
        local unpack_format = 'I' .. (length < 8 and length or 8)
        return string.unpack(unpack_format, self.VPDChip:Read(ctx.get_context_or_default(), offset, 
            length < 8 and length or 8))
    end)

    if not ok or not capcacity then
        log:error('Read chip failed')
        return
    end

    return capcacity // (1024 * 1024)
end

function c_nvme:update_res_id()
    local RESOURCE_ID_PCIE_SWTICH <const> = 0xFE
    local RESOURCE_ID_PCH <const> = 0xFF
    local ref_component = self.RefComponent
    if not ref_component then
        return
    end

    local PCIE_ADDR_INFO = '/bmc/kepler/Systems/1/PCIeDevices/PcieAddrInfo'
    local PCIE_ADDR_INFO_INTERFACE = 'bmc.kepler.Systems.PcieAddrInfo'

    local ok, obj_list =
        pcall(mdb.get_sub_objects, storage_bus.get_instance().bus, PCIE_ADDR_INFO, PCIE_ADDR_INFO_INTERFACE)
    if not ok then
        log:error('get PcieAddrInfo list failed')
        return
    end

    ok, ref_component =
        pcall(mdb.get_object, storage_bus.get_instance().bus, ref_component.path, 'bmc.kepler.Systems.Component')
    if not ok then
        log:error('get ref_component failed')
        return
    end

    local matched_flag = 0
    local tmp_cpu_id = 0
    for _, addr_info in pairs(obj_list) do
        if ref_component.Instance == addr_info.SlotID and
            ref_component.Type == addr_info.ComponentType then
            matched_flag = 1
            tmp_cpu_id = addr_info.SocketID
            if tmp_cpu_id == RESOURCE_ID_PCIE_SWTICH then
            -- 挂在PCH,不作处理
            elseif tmp_cpu_id == RESOURCE_ID_PCH then
                tmp_cpu_id = 0 -- 此时的0xff无效
            else
                tmp_cpu_id = tmp_cpu_id + 1
            end
            break
        end
    end

    if matched_flag == 0 then
        log:error('update ResourceId failed!')
        return
    end

    self.nvme_info.ResouceId = tmp_cpu_id
end

function c_nvme:generate_fault_maintenance_log(source, assert, fail_val)
    log:maintenance(log.MLOG_ERROR, common_def.FC_STORAGE_NO_IDENTIFY,
            "[%s] Physical drive %s detected Failure - %s, Register Byte: %s",
            source, self.Slot, assert, fail_val)
end

function c_nvme:generate_pre_fault_maintenance_log(source, assert, pre_fail_val)
    log:maintenance(log.MLOG_ERROR, common_def.FC_STORAGE_NO_IDENTIFY,
            "[%s] Physical drive %s detected Predictive Failure - %s, Register Byte: %s",
            source, self.Slot, assert, pre_fail_val)
end

function c_nvme:get_nvme_failure()
    -- self.Failure一直在刷新，赋值以避免维护日志记录的Failure值与实际进行判断的Failure值不一致
    local f = self.Failure
    if not f or not self.ManufacturerId or f == INVALID_READING then
        return 0
    end

    -- 如果读取异常不进行bit位判断
    if not self.SSDChip then
        log:error('invalid ssd_chip')
         return 0
    end

    local ok, _ = pcall(function ()
        return string.unpack('B', self.SSDChip:Read(ctx.get_context_or_default(), MI_FAILURE_OFFSET, 1))
    end)

    if not ok then
        return 0
    end

    -- 故障值刷新
    f = self.Failure
    if not f or not self.ManufacturerId or f == INVALID_READING then
        return 0
    end

    -- bit3 Port0 Link Active，1表示正常的
    -- bit5 Drive Functional ，1表示正常的
    -- bit6 Drive Not Ready，0是正常的
    local expression = 104 -- 01101000
    local expression_huawei = 96 -- 01100000
    local ssd_fault = ((f & expression) == 40) and 0 or 1 -- 40 为 00101000
    local ssd_huawei_fault =  ((f & expression_huawei) == 32) and 0 or 1 -- 32 为 00100000

    local res = self.ManufacturerId == 6629 and ssd_huawei_fault or ssd_fault
    local debounce_val = self.failure_debounce:get_debounced_val(res)
    if debounce_val ~= self.last_fail then
        log:notice('NVMe%s failure debounce val is %s, scanner val is %s', self.Slot, debounce_val, f)
        -- 记录硬盘 failure 告警维护日志
        self.last_fail = debounce_val
        self:generate_fault_maintenance_log('OOB', debounce_val == 1 and 'Asserted' or 'Deasserted', f)
    end

    return debounce_val
end

function c_nvme:get_nvme_pre_failure()
    local pf = self.PredictiveFailure
    if not pf or pf == INVALID_READING then
        return 0
    end

    -- 如果读取异常不进行bit位判断
    if not self.SSDChip then
        log:error('invalid ssd_chip')
         return 0
    end

    local ok, _ = pcall(function ()
        return string.unpack('B', self.SSDChip:Read(ctx.get_context_or_default(), MI_PRE_FAILURE_OFFSET, 1))
    end)

    if not ok then
        return 0
    end

    -- 重新刷新预故障值
    pf = self.PredictiveFailure
    if not pf or pf == INVALID_READING then
        return 0
    end
    -- 判断0、1、2、3、4bit位是否为 1
    local expression = 31 -- 00011111
    local ssd_pre_fault = ((pf & expression) == expression) and 0 or 1
    local debounce_val = self.pre_failure_debounce:get_debounced_val(ssd_pre_fault)

    if debounce_val ~= self.last_pre_fail then
        -- 记录硬盘predictive failure 告警维护日志
        log:notice('NVMe%s pre_failure debounce val is %s, scanner val is %s', self.Slot, debounce_val, pf)
        self.last_pre_fail = debounce_val
        self:generate_pre_fault_maintenance_log('OOB', debounce_val == 1 and 'Asserted' or 'Deasserted', pf)
    end

    return debounce_val
end

function c_nvme:get_firmeware_version()
    if not self.SSDChip then
        return
    end

    local revision = ''
    local ok, res
    -- 获取Revision依赖获取过ManufacturerId
    if self.ManufacturerId == common_def.INVALID_U32 then
        if self.Manufacturer == common_def.INVALID_STRING then
            local raw_manu = self:vpd_nvme_mi_get_product_info(common_def.RECORD_ITEM_MANUFACTURER_NAME)
            if type(raw_manu) == 'string' and utils_core.utf8_validate(raw_manu) and not nvme_utils.has_invisible_chars(raw_manu) then
                self.Manufacturer = raw_manu
            end
        end
        if self.Manufacturer ~= common_def.INVALID_STRING then
            self.ManufacturerId = self:vpd_nvme_mi_get_vender_id(self.Manufacturer) or common_def.INVALID_U32
        end
    end
    local ssd_firmware_offset = common_def.SSD_FIRMWARE_OFFSET_TABLE[self.ManufacturerId] or SSD_FIRMWARE_OFFSET
    for i = 0, SSD_FIRMWARE_LEN - 1 do
        ok, res = pcall(function ()
            local ascii_num = string.unpack('B', self.SSDChip:Read(ctx.get_context_or_default(),
                ssd_firmware_offset + i, 1))
            return string.char(ascii_num)
        end)

        if not ok then
            return
        end

        revision = revision .. res
    end

    revision = revision:match'^%s*(.*%S)' or ''
    return utils_core.utf8_validate(revision) and revision or ''
end


function c_nvme:get_nvme_info()
    if not self.SSDChip then
        return
    end
    local NVME_INFO_LEN <const> = 32
    local ok, data = pcall(function ()
        return self.SSDChip:Read(ctx.new(), 0, NVME_INFO_LEN)
    end)
    if not ok then
        log:error('get nvme info from SSDChip failed, error: %s', data)
        return
    end
    return data
end

function c_nvme:vpd_nvme_mi_get_info()
    self:update_static_prop()

    self.nvme_mi_info.MediaType = self.MediaType
    self.nvme_mi_info.Protocol = self.Protocol
    self.nvme_mi_info.Revision = self.Revision
    self.nvme_mi_info.Model = self.Model
    self.nvme_mi_info.SerialNumber = self.SerialNumber
    self.nvme_mi_info.Manufacturer = self.Manufacturer
    self.nvme_mi_info.ManufacturerId = self.ManufacturerId
    self.nvme_mi_info.CapableSpeedGbs = self.CapableSpeedGbs
    self.nvme_mi_info.CapacityMiB = self.CapacityMiB
    self.nvme_mi_info.NegotiatedSpeedGbs = self.NegotiatedSpeedGbs
    self.nvme_mi_info.Status = self.Status
    self.nvme_mi_info.LifeUsedPercentage = self.LifeUsedPercentage
    return self.nvme_mi_info
end

function c_nvme:vpd_raid_nvme_mi_get_info()
    self:update_raid_nvme_static_prop()
    self.nvme_mi_info.Manufacturer = self.Manufacturer
    return self.nvme_mi_info
end

function c_nvme:calc_capable_speed()
    local link_speed = self:vpd_nvme_mi_get_link_speed() or 0
    local link_width = self:vpd_nvme_mi_get_link_width() or common_def.INVALID_U8
    return nvme_max_speed2enum(link_speed * link_width)
end

function c_nvme:update_static_prop()
    if self.Model == common_def.INVALID_STRING then
        local raw_model = self:vpd_nvme_mi_get_product_info(common_def.RECORD_ITEM_MODEL_NUMBER)
        if type(raw_model) == 'string' and utils_core.utf8_validate(raw_model) and not nvme_utils.has_invisible_chars(raw_model) then 
            self.Model = raw_model
        end
    end

    if self.SerialNumber == common_def.INVALID_STRING then
        local raw_sn_str = self:vpd_nvme_mi_get_product_info(common_def.RECORD_ITEM_SERIAL_NUMBER)
        if type(raw_sn_str) == 'string' and utils_core.utf8_validate(raw_sn_str) and not nvme_utils.has_invisible_chars(raw_sn_str) then 
            self.SerialNumber = raw_sn_str
        end
    end

    if self.Manufacturer == common_def.INVALID_STRING then
        local raw_manu = self:vpd_nvme_mi_get_product_info(common_def.RECORD_ITEM_MANUFACTURER_NAME)
        if type(raw_manu) == 'string' and  utils_core.utf8_validate(raw_manu) and not nvme_utils.has_invisible_chars(raw_manu) then
            self.Manufacturer =  raw_manu
        end
    end

    if self.ManufacturerId == common_def.INVALID_U32 then
        self.ManufacturerId = self:vpd_nvme_mi_get_vender_id(self.Manufacturer) or common_def.INVALID_U32
    end

    if self.CapableSpeedGbs == common_def.INVALID_U8 then
        self.CapableSpeedGbs = self:calc_capable_speed()
    end

    if self.CapacityMiB == common_def.INVALID_U32 then
        self.CapacityMiB = self:vpd_nvme_mi_get_capacity() or common_def.INVALID_U32
    end
end

function c_nvme:update_raid_nvme_static_prop()
    if self.Manufacturer == common_def.INVALID_STRING then
        self.Manufacturer = self:vpd_nvme_mi_get_product_info(common_def.RECORD_ITEM_MANUFACTURER_NAME) or
            common_def.INVALID_STRING
    end
end

function c_nvme:nvme_vpd_get_info()
    -- 根据具体协议读取
    if self.protocol == common_def.NVME_VPD_PROTOCOL_NVME_MI then
        self.nvme_info = self:vpd_nvme_mi_get_info()
    else
        -- 其他协议
        if not self.ssd_form_factor then
            self.ssd_form_factor = c_ssd_form_factor.new(self.Slot, self.VPDChip)
        end
        self.nvme_info = self.ssd_form_factor:vpd_ssd_form_get_info(self)
    end
    self:update_res_id()
    local replica_drive = replica_drive_collection.get_instance():get_replica_drive_by_id(self.Slot)
    if replica_drive then
        replica_drive.on_update:emit(self.nvme_info)
    end
    return self.nvme_info
end

function c_nvme:raid_nvme_vpd_get_info()
    if self.protocol == common_def.NVME_VPD_PROTOCOL_NVME_MI then
        self.nvme_info = self:vpd_raid_nvme_mi_get_info()
    else
        if not self.ssd_form_factor then
            self.ssd_form_factor = c_ssd_form_factor.new(self.Slot, self.VPDChip)
        end

        self.nvme_info = self.ssd_form_factor:vpd_raid_ssd_form_get_info()
    end

    return self.nvme_info
end

-- 更新 NVMe 盘信息, 对外接口统一为 update_drive_info
function c_nvme:update_drive_info()
    if self.protocol == common_def.INVALID_U8 then
        self.protocol = self:pcie_nvme_get_protocol_type()
    end

    if self.protocol == common_def.INVALID_U8 then
        return
    end

    return self:nvme_vpd_get_info()
end

function c_nvme:update_raid_nvme_info()
    if self.protocol == common_def.INVALID_U8 then
        self.protocol = self:pcie_nvme_get_protocol_type()
    end

    if self.protocol == common_def.INVALID_U8 then
        return
    end

    return self:raid_nvme_vpd_get_info()
end

-- 更新NVMe盘smartlog信息
function c_nvme:get_nvme_smart_info()
    local obj = self.nvme_mi_mctp_obj
    if not obj or not obj.nvme_mi_obj then
        return
    end
    obj:update_smart_log()
    return obj.smart_log
end

-- 更新NVMe盘smartlog日志
function c_nvme:dump_nvme_smart_log(update_smart_log_flag)
    local obj = self.nvme_mi_mctp_obj
    if not obj or update_smart_log_flag then
        return
    end
    obj:collect_nvme_smart_log()
    return
end

local function get_hw_defined_smart_log_attr_list(log_page)
    local attr_list = {
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_HOST_WRITTEN_L] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_HOST_WRITTEN_H] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_TOTAL_SPARE_BLOCK] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_VALID_SPARE_BLOCK] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_TOTAL_SPARE_BLOCK] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_VALID_SPARE_BLOCK] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_USED_LIFESPAN] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_USED_LIFESPAN] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_NAND_WRITTEN_L] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_NAND_WRITTEN_H] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_PE_CYCLE] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_ERASE_COUNT] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_WEAROUT] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_PE_CYCLE] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_ERASE_COUNT] = common_def.INVALID_U32,
        [smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_WEAROUT] = common_def.INVALID_U32,
    }

    local match_count = 0
    local total_count = 0
    for _, _ in pairs(attr_list) do
        total_count = total_count + 1
    end
    local cur_loop = 0
    local max_loop = #log_page // NVME_MI_OPTIONS.HW_DEFINED_ENTRY_LEN

    local attr_entry
    while cur_loop < max_loop do
        cur_loop = cur_loop + 1
        attr_entry = NVME_MI_OPTIONS.attr_entry_data_structure:unpack(log_page, true)
        log_page = attr_entry.rest
        if not attr_list[attr_entry.attr_id] then
            goto continue
        end
        match_count = match_count + 1
        attr_list[attr_entry.attr_id] = {
            raw_value = attr_entry.raw_value,
            current_value = attr_entry.current_value
        }
        if match_count == total_count then
            break
        end
        ::continue::
    end
    return attr_list
end

local function get_smart_raw_by_attr_id(attr_list, attr_id)
    if attr_list[attr_id] == common_def.INVALID_U32 then
        return common_def.INVALID_U32
    end
    return attr_list[attr_id].raw_value
end

local function get_smart_avg_by_attr_id(attr_list, attr_id)
    local avg = get_smart_raw_by_attr_id(attr_list, attr_id)
    return avg == common_def.INVALID_U32 and common_def.INVALID_U32 or (avg >> 32)
end

local function get_smart_cur_by_attr_id(attr_list, attr_id)
    if attr_list[attr_id] == common_def.INVALID_U32 then
        return common_def.INVALID_U8
    end
    return attr_list[attr_id].current_value
end

-- 优先从0x5c和0x69获取，其次从0x4e和0x4f获取
local function get_remnant_wearout(attr_list)
    local tlc_lifespan = get_smart_cur_by_attr_id(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_WEAROUT)
    local slc_lifespan = get_smart_cur_by_attr_id(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_WEAROUT)
    if tlc_lifespan <= common_def.PERCENTAGE_UPPER_LIMIT or slc_lifespan <= common_def.PERCENTAGE_UPPER_LIMIT then
        return tlc_lifespan > slc_lifespan and slc_lifespan or tlc_lifespan
    end

    tlc_lifespan = get_smart_cur_by_attr_id(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_USED_LIFESPAN)
    slc_lifespan = get_smart_cur_by_attr_id(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_USED_LIFESPAN)
    if tlc_lifespan <= common_def.PERCENTAGE_UPPER_LIMIT or slc_lifespan <= common_def.PERCENTAGE_UPPER_LIMIT then
        return tlc_lifespan > slc_lifespan and slc_lifespan or tlc_lifespan
    end
    return common_def.INVALID_U8
end

local function get_spare_block_percent(attr_list, origin, cur)
    local valid_spare_block = get_smart_raw_by_attr_id(attr_list, cur)
    local total_spare_block = get_smart_raw_by_attr_id(attr_list, origin)
    if valid_spare_block ~= common_def.INVALID_U32 and total_spare_block ~= common_def.INVALID_U32 and
        total_spare_block ~=0 then
        return (valid_spare_block * 100) // total_spare_block
    end
    return common_def.INVALID_U8
end

local function get_lifespan_str(attr_list, attr_id)
    local lifespan = get_smart_raw_by_attr_id(attr_list, attr_id)
    if lifespan > common_def.PERCENTAGE_UPPER_LIMIT * common_def.PERCENTAGE_UPPER_LIMIT then
        return common_def.INVALID_NUM2STR
    end
    return string.format('%.2f', lifespan / 100)
end

-- 更新NVMe盘hw_defined smart log信息
function c_nvme:get_nvme_hw_defined_smart_info()
    local obj = self.nvme_mi_mctp_obj
    if not obj then
        return
    end
    if self.support_hw_defined_smart_log ~= NVME_MI_OPTIONS.SUPPORT_HW_DEFINED_WITH_UUID and
        self.support_hw_defined_smart_log ~= NVME_MI_OPTIONS.SUPPORT_HW_DEFINED_WITHOUT_UUID then
        return
    end
    local ret = nvme_admin_command.get_hw_defined_smart_log(obj, self.uuid_index)
    if not ret then
        return
    end

    local attr_list = get_hw_defined_smart_log_attr_list(ret)
    return {
        remnant_wearout = get_remnant_wearout(attr_list),
        slc_avg_ec = get_smart_avg_by_attr_id(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_ERASE_COUNT),
        tlc_avg_ec = get_smart_avg_by_attr_id(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_ERASE_COUNT),
        slc_pe_cycle = get_smart_raw_by_attr_id(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_PE_CYCLE),
        tlc_pe_cycle = get_smart_raw_by_attr_id(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_PE_CYCLE),
        slc_spare_block = get_spare_block_percent(attr_list,
            smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_TOTAL_SPARE_BLOCK,
            smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_VALID_SPARE_BLOCK),
        tlc_spare_block = get_spare_block_percent(attr_list,
            smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_TOTAL_SPARE_BLOCK,
            smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_VALID_SPARE_BLOCK),
        slc_used_lifespan = get_lifespan_str(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_SLC_USED_LIFESPAN),
        tlc_used_lifespan = get_lifespan_str(attr_list, smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_USED_LIFESPAN),
        hw_defined_nand_write_l = get_smart_raw_by_attr_id(attr_list,
            smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_NAND_WRITTEN_L),
        hw_defined_nand_write_h = get_smart_raw_by_attr_id(attr_list,
            smart_def.HW_DEFINED_SMART_ATTRIBUTE_ID_TLC_NAND_WRITTEN_H),
    }
end

function c_nvme:update_dynamic_drive_info()
    local dynamic_info = {}
    dynamic_info.TemperatureCelsius =
        (self.TemperatureCelsius > 128) and (self.TemperatureCelsius - 256) or self.TemperatureCelsius
    dynamic_info.PredictiveFailure, dynamic_info.Failure = self:pciessd_predictive_failure_and_fault_alarm()
    dynamic_info.PredictedMediaLifeLeftPercent =
        (self.PredictedMediaLifeLeftPercent > 100) and 255 or (100 - self.PredictedMediaLifeLeftPercent)
    dynamic_info.PowerOnHours = self.PowerOnHours
    dynamic_info.MediaErrorCount = self.MediaErrorCount
    dynamic_info.Revision = self:get_firmeware_version() or 'N/A'
    dynamic_info.link_fault = self.link_fault == 1
    dynamic_info.Status = self.Status
    dynamic_info.LifeUsedPercentage = self.LifeUsedPercentage
    dynamic_info.SpareBlockPercentage = self.SpareBlockPercentage
    return dynamic_info
end


-- 判断是否支持通过uuid index获取hw defined smart log
local function check_support_hw_defined_by_uuid(obj)
    if not obj or not obj.nvme_mi_obj then
        return false
    end
    local ok, ret = pcall(nvme_admin_command_process.check_support_uuid_by_identify_ctrl, obj)
    if not ok or not ret then
        log:notice('Disk%s not support uuid by id_ctrl err:%s', obj.nvme.Slot, ret)
        return false
    end

    ok, ret = pcall(nvme_admin_command_process.check_specified_opcode_support_uuid, obj,
        NVME_MI_OPTIONS.NVME_ADMIN_COMMANDS_OPCODE.NVME_ADMIN_GET_LOG_PAGE)
    if not ok or not ret then
        log:notice('Disk%s specified opcode not support uuid by support_effects_log err:%s', obj.nvme.Slot, ret)
        return false
    end
    local uuid_index
    ok, uuid_index = pcall(nvme_admin_command_process.get_specified_uuid_index, obj,
        NVME_MI_OPTIONS.HW_DEFINED_UUID_LL, NVME_MI_OPTIONS.HW_DEFINED_UUID_LH,
        NVME_MI_OPTIONS.HW_DEFINED_UUID_HL, NVME_MI_OPTIONS.HW_DEFINED_UUID_HH)
    if not ok or not uuid_index then
        log:notice('Disk%s not find specified uuid err:%s', obj.nvme.Slot, uuid_index)
        return false
    end
    return true, uuid_index
end

function c_nvme:check_support_hw_defined_smart_log()
    local obj = self.nvme_mi_mctp_obj
    if not obj or not obj.nvme_mi_obj then
        return common_def.INVALID_U8
    end

    nvme_mi_command.get_controller_list(obj)
    local ret, uuid_index = check_support_hw_defined_by_uuid(obj)
    if ret then
        self.uuid_index = uuid_index
        return NVME_MI_OPTIONS.SUPPORT_HW_DEFINED_WITH_UUID
    end
    ret = nvme_admin_command_process.check_support_hw_defined_without_uuid(obj)
    log:notice('Disk%s %s support hw defined smart log without uuid index', obj.nvme.Slot, ret and '' or 'not')
    return ret and NVME_MI_OPTIONS.SUPPORT_HW_DEFINED_WITHOUT_UUID or NVME_MI_OPTIONS.NOT_SUPPORT_HW_DEFINED
end

local function check_collect_est_lifespan_time(obj)
    local UPDATE_INTERVAL_OS_24_HOURS<const> = (3600 * 24) -- 24小时记录一次写放大信息
    local last = obj.est_lifespan_last_up_time
    local current = math.floor(os.time())
    if last == 0 then
        obj.write_amplification_info.first_start_flag = 1
        obj.est_lifespan_last_up_time = current
        return true
    end

    if ((current > last) and (current - last) > UPDATE_INTERVAL_OS_24_HOURS) or
        ((last > current) and (common_def.INVALID_U32 - last + current) > UPDATE_INTERVAL_OS_24_HOURS) then
        obj.est_lifespan_last_up_time = current
        return true
    end
    return false
end

function c_nvme:init_nvme_mi_mctp(drive)
    if self.protocol == common_def.INVALID_U8 or self.is_init_nvmemi_mctp then
        return
    end
    self.is_init_nvmemi_mctp = true
    -- 先判断是否支持mctp再创建对象
    if not self:check_support_mctp() then
        return
    end
    if not self.nvme_mi_mctp_obj then
        self.nvme_mi_mctp_obj = nvme_mctp.new(self)
        self.nvme_mi_mctp_obj:create_nvmemi_endpoint()
        if not self.nvme_mi_mctp_obj.nvme_mi_obj then
            local position = self:get_position()
            log:notice("Disk %s build mctp over smbus during init, position=%s", self.Slot, position)
            self.nvme_mi_mctp_obj:create_mctp_endpoint_for_smbus(position)
        end
    end

    self.support_hw_defined_smart_log = self:check_support_hw_defined_smart_log()
    if self.support_hw_defined_smart_log ~= NVME_MI_OPTIONS.NOT_SUPPORT_HW_DEFINED then
        utils.msleep(5000)
        self:new_task({TASK_DUMP_WRITE_AMP, self.Slot}):loop(function(task)
            if drive.write_amplification_info.update_support_flag ~= 1 or
                not check_collect_est_lifespan_time(drive) then
                return
            end
            drive:record_write_amp_log(true)
        end):set_timeout_ms(UPDATE_INTERVAL_30_MINS)
    end
    self:new_task({TASK_UPDATE_NVME_MCTP, self.Slot}):loop(function(task)
        if task.is_exit then
            return
        end
        self.nvme_mi_mctp_obj:update_mctp_properties()
    end):set_timeout_ms(UPDATE_INTERVAL_24_HOURS)
end

function c_nvme:check_support_mctp()
    local ok, capcacity = pcall(function ()
        local hex_str = utils.to_hex(self.VPDChip:Read(ctx.get_context_or_default(), 0, 250))
        local number_arr = string_split(hex_str, ' ', 16)
        return number_arr
    end)
    if not ok or not capcacity then
        log:error('Read chip failed')
        return false
    end
    local support
    if capcacity[capcacity[VENDOR_SPECIFIC] * 8 + 1] == MULTIRECORDAREA then
        if capcacity[(capcacity[VENDOR_SPECIFIC] * 8 + 1) + 64] == PORTMULTIRECORD then
            -- 该字节为12时，再偏移10字节的数值为是否支持mctp
            support = capcacity[(capcacity[VENDOR_SPECIFIC] * 8 + 1) + 64 + 10]
        end
    elseif capcacity[capcacity[VENDOR_SPECIFIC] * 8 + 1] == PORTMULTIRECORD then
        support = capcacity[(capcacity[VENDOR_SPECIFIC] * 8 + 1) + 10]
    end
    if support ~= MCTP_SUPPORT and self.Model ~= 'DAPUSTOR DPHV5508T0TB06T4000' then
        log:error('not support nvme mi over mctp, Slot: %s, support: %s', self.Slot, support)
        return false
    end
    return true
end

function c_nvme:update_nvme_endpoint()
    if self.protocol ~= common_def.NVME_VPD_PROTOCOL_NVME_MI then
        return
    end

    if not self:check_support_mctp() then
        return
    end
    if not self.nvme_mi_mctp_obj then
        self.nvme_mi_mctp_obj = nvme_mctp.new(self)
    end
    self.nvme_mi_mctp_obj:create_nvmemi_endpoint()
    if not self.nvme_mi_mctp_obj.nvme_mi_obj then
        local position = self:get_position()
        log:notice("Disk %s build mctp over smbus during os restart, position=%s", self.Slot, position)
        self.nvme_mi_mctp_obj:create_mctp_endpoint_for_smbus(position)
    end

    self.support_hw_defined_smart_log = self:check_support_hw_defined_smart_log()
end

function c_nvme:ctor()
    self.on_presence_changed = signal.new() -- 在位状态变化信号
    self.on_smbios_status_changed = signal.new()
    -- 取代HealthCode。以下4个bit位按使能的条件取反
    self.drive_not_ready = 1   --bit6
    self.drive_functional = 0   --bit5
    self.reset_not_required = 0   --bit4
    self.port0_pcie_link_active = 0   --bit3
    self.link_fault = 0
    self.init_state = true
    self.Model = common_def.INVALID_STRING
    self.Manufacturer = common_def.INVALID_STRING
    self.ManufacturerId = common_def.INVALID_U32
    self.PCIeLinkSpeed = 0
    self.CapableSpeedGbs = common_def.INVALID_U8
    self.CapacityMiB = common_def.INVALID_U32
    self.PowerOnHours = common_def.INVALID_U32
    self.MediaErrorCount = common_def.INVALID_U32
    self.last_fail = 0
    self.last_pre_fail = 0
    self.update_time =0
    self.failure_debounce = debounce[debounce.DEBOUNCED_CONT].new(5, 0) -- 5 次防抖
    self.pre_failure_debounce = debounce[debounce.DEBOUNCED_CONT].new(5, 0) -- 5 次防抖
    self.port0_pcie_link_active_debounce = debounce[debounce.DEBOUNCED_CONT_BIN].new(5, 5, self.port0_pcie_link_active)
    self.drive_functional_debounce = debounce[debounce.DEBOUNCED_CONT_BIN].new(5, 5, self.drive_functional)
    self.drive_not_ready_debounce = debounce[debounce.DEBOUNCED_CONT_BIN].new(20, 3, self.drive_not_ready)
    self.link_fault_debounce = debounce[debounce.DEBOUNCED_CONT_BIN].new(5, 5, self.link_fault)
    self.protocol = common_def.INVALID_U8
    self.NegotiatedSpeedGbs = common_def.INVALID_U8
    self.nvme_mi_info = {}
    self.ssd_form_factor = false
    self.nvme_info = {}
    self.is_init_nvmemi_mctp = false
    self.nvme_mi_mctp_obj = false
    self.LifeUsedPercentage  = common_def.INVALID_U8
    self.Status = common_def.INVALID_U8
    self.SpareBlockPercentage = common_def.INVALID_U8
    self.support_hw_defined_smart_log = common_def.INVALID_U8
    self.uuid_index = 0
    -- 兼容单主机场景，system_id默认为1
    self.system_id = 1
end

function c_nvme:dtor()
    self:stop_tasks()
end

function c_nvme:init()
    -- model.json里面声明过的属性不在ctor里面进行初始化赋值，否则该属性无法被监听
    c_nvme.super.init(self)
    self.SerialNumber = common_def.INVALID_STRING
    self.on_smbios_status_changed:on(function ()
        log:notice('NVMe:%s update endpoint.', self.Slot)
        self:new_task({TASK_UPDATE_NVME_EP, self.Slot}):loop(function(task)
            pcall(self.update_nvme_endpoint, self)
            task:stop()
        end)
    end)
end

return c_nvme